var n = [{
  Text: [{
    string: "冒泡排序是通过多次的比较和交换逐渐将序列排序的方法。越小的元素会经由交换慢慢“浮”到数列的顶端（左边）。",
    continue: !0
  }, {
    string: "冒泡排序的每次操作都会对相邻的两个数据进行比较，如果不满足大小关系，则交换这两个数。",
    continue: !0
  }, {
    string: "我们从后往前依次开始比较, 每次比较两个数。 如果前左边的数大于右边的数，则交换这两个数的位置。",
    continue: !0
  }, {
    string: "我们从右向左开始冒泡，首先选中7 和 5。",
    continue: !1
  }, {
    string: "由于7 大于 5，我们将 7 和 5 的位置交换。",
    continue: !1
  }, {
    string: "然后继续向前进行下一次比较。",
    continue: !0
  }, {
    string: "接下来选中 6 和 5。",
    continue: !1
  }, {
    string: "由于6 大于 5，我们将 6 和 5 的位置交换。",
    continue: !1
  }, {
    string: "选中 3 和 5， 由于 3 小于 5， 所以位置不变。",
    continue: !1
  }, {
    string: "继续向前冒泡。",
    continue: !1
  }, {
    string: "如果前面的数大于后面的数，则交换这两个数的位置。否则继续前进。",
    continue: !1
  }, {
    string: "如果前面的数大于后面的数，则交换这两个数的位置。否则继续前进。",
    continue: !1
  }, {
    string: "如果前面的数大于后面的数，则交换这两个数的位置。否则继续前进。",
    continue: !1
  }, {
    string: "如果前面的数大于后面的数，则交换这两个数的位置。否则继续前进。",
    continue: !1
  }, {
    string: "如果前面的数大于后面的数，则交换这两个数的位置。否则继续前进。",
    continue: !1
  }, {
    string: "如果前面的数大于后面的数，则交换这两个数的位置。否则继续前进。",
    continue: !1
  }, {
    string: "如果前面的数大于后面的数，则交换这两个数的位置。否则继续前进。",
    continue: !1
  }, {
    string: "经过第一轮冒泡之后之后，最小的数字 1 已经在序列的最左边, 已经被排序。",
    continue: !1
  }, {
    string: "我们回到序列的最右边，开始下一轮冒泡。",
    continue: !0
  }, {
    string: "选中 7 和 6，由于 7 大于 6， 所以位置不变。",
    continue: !0
  }, {
    string: "选中 6 和 5，由于 6 大于 5， 所以位置不变。",
    continue: !1
  }, {
    string: "选中 5 和 3，由于 5 大于 3， 所以位置不变。",
    continue: !1
  }, {
    string: "选中 3 和 4，由于 3 小于于 4， 我们将 3 和 4 的位置交换。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "继续冒泡，直到所有的数都被排序。",
    continue: !1
  }, {
    string: "现在，所有数字都被排序。",
    continue: !1
  }, {
    string: "到这里，我们完成了冒泡排序的演示。",
    continue: !1
  }],
  size: 70
}],
    i = [{
  Text: [{
    string: "选择排序是一种简单直观的排序算法。它的工作原理是，每次遍历序列，找到最小（大）的数，放在序列的最前面（与最前面的数交换位置）。然后再对剩下未排序的序列重复上述过程。",
    continue: !0
  }, {
    string: "我们第一次遍历序列，找到最小的数字 1。",
    continue: !1
  }, {
    string: "将它与最左侧的数字交换，并标记为已排序。",
    continue: !1
  }, {
    string: "第二次遍历序列，找到最小的数字 2。",
    continue: !1
  }, {
    string: "将它与最左侧的数字交换，并标记为已排序。",
    continue: !1
  }, {
    string: "第二次遍历序列，找到最小的数字 3。",
    continue: !1
  }, {
    string: "将它与最左侧的数字交换，并标记为已排序。",
    continue: !1
  }, {
    string: "然后继续遍历剩下的序列，直到所有数字都被放在正确的位置。",
    continue: !1
  }],
  size: 8
}],
    t = [{
  Text: [{
    string: "插入排序的基本操作就是从未排序的序列中取出一个数，插入到已经排序序列的正确位置中。",
    continue: !0
  }, {
    string: "我们从左到右顺序遍历未排序的序列，将它们插入到下方的有序序列中（开始时有序序列为空）。",
    continue: !0
  }, {
    string: "首先，我们取出数字 8。此时有序序列为空，因此我们直接将 8 插入到下方的有序序列。",
    continue: !1
  }, {
    string: "取出数字 9，与下方的有序序列进行比较。",
    continue: !1
  }, {
    string: "因为 9 大于 8, 我们将它插入到 8 的右边。",
    continue: !1
  }, {
    string: "取出数字 1，与下方的有序序列进行比较。",
    continue: !1
  }, {
    string: "因为 1 小于 8, 我们将它插入到 8 的左边。",
    continue: !1
  }, {
    string: "取出数字 4，与下方的有序序列进行比较。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 4 的数。",
    continue: !1
  }, {
    string: "因为 4 小于 8, 我们将它插入到 8 的左边。",
    continue: !1
  }, {
    string: "取出数字 2，与下方的有序序列进行比较。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 2 的数。",
    continue: !1
  }, {
    string: "因为 2 小于 4, 我们将它插入到 4 的左边。",
    continue: !1
  }, {
    string: "取出数字 3，与下方的有序序列进行比较。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 3 的数。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 3 的数。",
    continue: !1
  }, {
    string: "因为 3 小于 4, 我们将它插入到 4 的左边。",
    continue: !1
  }, {
    string: "取出数字 6，与下方的有序序列进行比较。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 6 的数。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 6 的数。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 6 的数。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 6 的数。",
    continue: !1
  }, {
    string: "因为 6 小于 8, 我们将它插入到 8 的左边。",
    continue: !1
  }, {
    string: "取出数字 7，与下方的有序序列进行比较。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 7 的数。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 7 的数。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 7 的数。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 7 的数。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 7 的数。",
    continue: !1
  }, {
    string: "因为 7 小于 8, 我们将它插入到 8 的左边。",
    continue: !1
  }, {
    string: "取出数字 5，与下方的有序序列进行比较。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 5 的数。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 5 的数。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 5 的数。",
    continue: !1
  }, {
    string: "移动光标，直到找到第一个大于 5 的数。",
    continue: !1
  }, {
    string: "因为 5 小于 6, 我们将它插入到 6 的左边。",
    continue: !1
  }, {
    string: "此时，所有的数字都被正确插入到了下方有序序列中。",
    continue: !0
  }, {
    string: "到这里，我们完成了插入排序的演示。",
    continue: !1
  }],
  size: 38
}],
    e = [{
  Text: [{
    string: "堆排序是利用堆这种数据结构设计的一种排序算法，它是一种特殊的选择排序。可以利用堆的特点快速定位指定索引的元素。",
    continue: !0
  }, {
    string: "这里我们使用的是大顶堆，即每个节点都大于它左右子节点的值，根结点是最大的节点。",
    continue: !0
  }, {
    string: "首先，按照降序构建大顶堆。这里需要用到堆的插入操作，关于如何通过插入操作构建大顶堆，请参考数据结构中‘堆’的内容。",
    continue: !0
  }, {
    string: "首先，按照降序构建大顶堆。这里需要用到堆的插入操作，关于如何通过插入操作构建大顶堆，请参考数据结构中‘堆’的内容。",
    continue: !1
  }, {
    string: "首先，按照降序构建大顶堆。这里需要用到堆的插入操作，关于如何通过插入操作构建大顶堆，请参考数据结构中‘堆’的内容。",
    continue: !1
  }, {
    string: "首先，按照降序构建大顶堆。这里需要用到堆的插入操作，关于如何通过插入操作构建大顶堆，请参考数据结构中‘堆’的内容。",
    continue: !1
  }, {
    string: "首先，按照降序构建大顶堆。这里需要用到堆的插入操作，关于如何通过插入操作构建大顶堆，请参考数据结构中‘堆’的内容。",
    continue: !1
  }, {
    string: "首先，按照降序构建大顶堆。这里需要用到堆的插入操作，关于如何通过插入操作构建大顶堆，请参考数据结构中‘堆’的内容。",
    continue: !1
  }, {
    string: "首先，按照降序构建大顶堆。这里需要用到堆的插入操作，关于如何通过插入操作构建大顶堆，请参考数据结构中‘堆’的内容。",
    continue: !1
  }, {
    string: "首先，按照降序构建大顶堆。这里需要用到堆的插入操作，关于如何通过插入操作构建大顶堆，请参考数据结构中‘堆’的内容。",
    continue: !1
  }, {
    string: "首先，按照降序构建大顶堆。这里需要用到堆的插入操作，关于如何通过插入操作构建大顶堆，请参考数据结构中‘堆’的内容。",
    continue: !1
  }, {
    string: "首先，按照降序构建大顶堆。这里需要用到堆的插入操作，关于如何通过插入操作构建大顶堆，请参考数据结构中‘堆’的内容。",
    continue: !1
  }, {
    string: "到这里，堆构造完毕。此时最大的数据 7 处于堆堆顶部。下面我们每次从堆顶取走最大的数字，然后再通过交换操作维持堆的平衡（将最大的数字换至堆顶）。",
    continue: !0
  }, {
    string: "我们取出 7，将它移动至上方最右侧，表示它已经被排序。同时将右下方的数字 5 交换到堆的顶部。",
    continue: !1
  }, {
    string: "此时需要通过交换操作来维持堆的平衡。",
    continue: !1
  }, {
    string: "继续取出堆顶的数据 6。将右下方的数字 3 交换到堆的顶部。",
    continue: !1
  }, {
    string: "通过交换操作来维持堆的平衡。",
    continue: !1
  }, {
    string: "重复上述步骤，直到取出所有的数。",
    continue: !1
  }, {
    string: "重复上述步骤，直到取出所有的数。",
    continue: !1
  }, {
    string: "重复上述步骤，直到取出所有的数。",
    continue: !1
  }, {
    string: "重复上述步骤，直到取出所有的数。",
    continue: !1
  }, {
    string: "重复上述步骤，直到取出所有的数。",
    continue: !1
  }, {
    string: "重复上述步骤，直到取出所有的数。",
    continue: !1
  }, {
    string: "重复上述步骤，直到取出所有的数。",
    continue: !1
  }, {
    string: "重复上述步骤，直到取出所有的数。",
    continue: !1
  }, {
    string: "此时，我们按照降序取出了堆中所有的数据。",
    continue: !0
  }, {
    string: "到这里，我们完成了堆排序的演示。",
    continue: !1
  }],
  size: 27
}],
    r = [{
  Text: [{
    string: "希尔排序是以它的发明者Donald Shell命名的。希尔排序，也称递减增量排序算法，是插入排序的一种更高效的改进版本。",
    continue: !0
  }, {
    string: "希尔排序是针对插入排序的以下两个特点而进行改进的：1.普通插入排序一般来说是低效的，因为插入排序每次只能将数据移动一位。2.而插入排序在对有序程度高的数据进行操作时，效率较高。",
    continue: !0
  }, {
    string: "希尔排序的基本思想是：先将整个待排序的序列分割成为若干子序列，分别进行插入排序，待整个序列中的数据基本有序时，再对序列进行依次直接插入排序。",
    continue: !0
  }, {
    string: "下面我们试着使用希尔排序对上面的黄色序列进行排序。",
    continue: !0
  }, {
    string: "首先，我们以增量 4 将上面的待排序序列分为四组。即每组之间的间隔为 4 个数据。",
    continue: !1
  }, {
    string: "将这四组数据用四种不同的颜色进行标记。然后对每一组数据进行插入排序。",
    continue: !0
  }, {
    string: "首先选中第一组数据。",
    continue: !1
  }, {
    string: "对其进行插入排序。",
    continue: !1
  }, {
    string: "对其进行插入排序。",
    continue: !1
  }, {
    string: "对其进行插入排序。",
    continue: !1
  }, {
    string: "对其进行插入排序。",
    continue: !1
  }, {
    string: "再选中第二组数据。",
    continue: !1
  }, {
    string: "对第二组数据进行插入排序。",
    continue: !1
  }, {
    string: "对第二组数据进行插入排序。",
    continue: !1
  }, {
    string: "对第二组数据进行插入排序。",
    continue: !1
  }, {
    string: "对第二组数据进行插入排序。",
    continue: !1
  }, {
    string: "再选中第三组数据。",
    continue: !1
  }, {
    string: "对第三组数据进行插入排序。",
    continue: !1
  }, {
    string: "对第三组数据进行插入排序。",
    continue: !1
  }, {
    string: "对第三组数据进行插入排序。",
    continue: !1
  }, {
    string: "对第三组数据进行插入排序。",
    continue: !1
  }, {
    string: "再选中第四组数据。",
    continue: !1
  }, {
    string: "对第四组数据进行插入排序。",
    continue: !1
  }, {
    string: "对第四组数据进行插入排序。",
    continue: !1
  }, {
    string: "对第四组数据进行插入排序。",
    continue: !1
  }, {
    string: "对第四组数据进行插入排序。",
    continue: !1
  }, {
    string: "此时我们完成了以 4 为增量的分组插入排序。",
    continue: !1
  }, {
    string: "然后我们将这四组数据进行合并。",
    continue: !1
  }, {
    string: "可以看到，经过过一轮排序后，序列已经有序多了。",
    continue: !0
  }, {
    string: "现在，我们再以增量 2 将上面的待排序序列分为两组。即每组之间的间隔为两个数据。",
    continue: !1
  }, {
    string: "首先选中第一组数据。",
    continue: !1
  }, {
    string: "对其进行插入排序。",
    continue: !1
  }, {
    string: "对其进行插入排序。",
    continue: !1
  }, {
    string: "再选中第二组数据。",
    continue: !1
  }, {
    string: "对第二组数据进行插入排序。",
    continue: !1
  }, {
    string: "对第二组数据进行插入排序。",
    continue: !1
  }, {
    string: "然后我们将这四组数据进行合并。",
    continue: !1
  }, {
    string: "此时我们完成了以 2 为增量的分组插入排序。现在，经过第二轮排序后，序列已经基本有序了。",
    continue: !1
  }, {
    string: "最后，我们对这个已经基本有序的序列进行增量为 1 的插入排序， 即顺序插入排序。",
    continue: !0
  }, {
    string: "最后，我们对这个已经基本有序的序列进行增量为 1 的插入排序， 即顺序插入排序。",
    continue: !1
  }, {
    string: "此时数据已经完全有序。我们可以看到，在希尔排序的过程中，随着增量的减小，每次分组后序列的有序度越高，插入排序所需要的移位操作就越少。",
    continue: !1
  }, {
    string: "到这里，我们完成了希尔排序的演示。",
    continue: !1
  }],
  size: 42
}],
    s = [{
  Text: [{
    string: "快速排序的基本思想是，通过一次排序将要排序的数据分割成独立的两部分，其中一部分的所有数据都比另外一部分的所有数据都要小，然后再用同样的方法对这两部分数据分别进行快速排序。",
    continue: !0
  }, {
    string: "每次排序之前，选择一个数作为基准（P）, 目标是以P为中间值，将数据分为大小两部分。",
    continue: !0
  }, {
    string: "为了简单，这里我们选择最右侧的数据作为基准(p)，同时在剩下的数据中标记最左边(L)和最右边(R)的数。",
    continue: !1
  }, {
    string: "然后将左光标(L)向右移动。",
    continue: !1
  }, {
    string: "继续移动，直到光标对应的数字大于P的值, 或者光标与P碰撞（左光标与右光标碰撞不会停止移动）。",
    continue: !1
  }, {
    string: "这里因为8 > 6, 停止移动。",
    continue: !0
  }, {
    string: "然后将右光标向(R)左移动，直到光标对应的数字小于P的值。",
    continue: !1
  }, {
    string: "这里因为4 < 6, 停止移动。",
    continue: !0
  }, {
    string: "当左右侧的光标都停止时，交换两边的数字。",
    continue: !0
  }, {
    string: null,
    continue: !1
  }, {
    string: "继续移动左光标，直到光标对应的数字大于P的值。",
    continue: !1
  }, {
    string: "继续移动左光标，直到光标对应的数字大于P的值。",
    continue: !1
  }, {
    string: "继续移动左光标，直到光标对应的数字大于P的值。",
    continue: !1
  }, {
    string: "这里因为9 > 6, 停止移动。",
    continue: !0
  }, {
    string: "继续移动右光标。",
    continue: !1
  }, {
    string: "当左右光标(R) 与左光标(L)重叠时，停止移动。",
    continue: !0
  }, {
    string: "交换左右光标对应的数和P对应的数。",
    continue: !1
  }, {
    string: "此时数字6已经被排序，并且将序列分成了大小两个序列（6左侧的序列小于右侧的序列）。",
    continue: !1
  }, {
    string: "然后我们分别对左侧和右侧的序列使用同样的方法进行排序。",
    continue: !0
  }, {
    string: "我们先从左侧开始。",
    continue: !1
  }, {
    string: "同样的，我们选择右侧的数据作为基准(P)，同时在剩下的数据中标记最左边(L)和最右边(R)的数。",
    continue: !1
  }, {
    string: "因为此时 L 对应的数字 3 大于 P 所对应的数字 2， 所以不需要移动。",
    continue: !0
  }, {
    string: "同样的，此时 R 对应的数字 1 小于于 P 所对应的数字 2， 所以也不需要移动。",
    continue: !0
  }, {
    string: "当左右侧的光标都停止时，交换两边的数字。",
    continue: !1
  }, {
    string: "继续移动左光标，直到光标对应的数字大于P的值。",
    continue: !1
  }, {
    string: "继续移动右光标。",
    continue: !1
  }, {
    string: "当左右光标(R) 与左光标(L)重叠时，停止移动。",
    continue: !1
  }, {
    string: "交换左右光标对应的数和P对应的数。",
    continue: !1
  }, {
    string: "此时数字 2 已经被排序，并且将序列分成了大小两个序列。",
    continue: !1
  }, {
    string: "由于左侧只有一个数字 1， 所以不需要进行排序。",
    continue: !1
  }, {
    string: "下面我们使用同样的方法对 2 右侧的数据进行排序。",
    continue: !1
  }, {
    string: "下面我们使用同样的方法对 2 右侧的数据进行排序。",
    continue: !1
  }, {
    string: "注意：左光标与右光标发生碰撞不会停止移动，这一点和右光标与左光标碰撞不同。",
    continue: !0
  }, {
    string: "注意：左光标与右光标发生碰撞不会停止移动，这一点和右光标与左光标碰撞不同。",
    continue: !1
  }, {
    string: "当左光标到达序列最右边时停止移动。",
    continue: !0
  }, {
    string: "这表示右侧的 P 是序列中最大的数字。",
    continue: !0
  }, {
    string: "所以当左光标到达序列的最右边的时候，P 就已经在正确的位置，这一轮的排序完成。",
    continue: !1
  }, {
    string: "然后我们用相同的方法，对剩下的数据进行排序。",
    continue: !1
  }, {
    string: "然后我们用相同的方法，对剩下的数据进行排序。",
    continue: !1
  }, {
    string: "然后我们用相同的方法，对剩下的数据进行排序。",
    continue: !1
  }, {
    string: "然后我们用相同的方法，对剩下的数据进行排序。",
    continue: !1
  }, {
    string: "现在6 左侧的数据已经全部被排序，下面我们对右侧数据进行排序。",
    continue: !1
  }, {
    string: "重复相同的方法，直到所有数字都被排序。",
    continue: !1
  }, {
    string: "重复相同的方法，直到所有数字都被排序。",
    continue: !1
  }, {
    string: "重复相同的方法，直到所有数字都被排序。",
    continue: !1
  }, {
    string: "重复相同的方法，直到所有数字都被排序。",
    continue: !1
  }, {
    string: "重复相同的方法，直到所有数字都被排序。",
    continue: !1
  }, {
    string: "重复相同的方法，直到所有数字都被排序。",
    continue: !1
  }, {
    string: "重复相同的方法，直到所有数字都被排序。",
    continue: !1
  }, {
    string: "到这里，我们完成了快速排序的演示。",
    continue: !1
  }],
  size: 50
}],
    u = [{
  Text: [{
    string: "归并排序是建立在归并操作上的一种有效的排序算法, 该算法是采用分治思想的一个非常典型的应用。",
    continue: !0
  }, {
    string: "归并算法的思想是，将序列分解成最小有序子序列，再逐个将有序的子序列合并，得到有序的子序列段。然后再合并子序列段，最终得到一个完整的有序序列。",
    continue: !0
  }, {
    string: "这里，我们将图中的 9 个数字分为 5 组，用虚线隔开。",
    continue: !1
  }, {
    string: "然后将这 5 组数据分别在内部进行排序。由于数据较少，可以分别采用较为简单的插入排序。",
    continue: !0
  }, {
    string: "然后将这 5 组数据分别在内部进行排序。由于数据较少，可以分别采用较为简单的插入排序。",
    continue: !1
  }, {
    string: "然后将这 5 组数据分别在内部进行排序。由于数据较少，可以分别采用较为简单的插入排序。",
    continue: !1
  }, {
    string: "然后将这 5 组数据分别在内部进行排序。由于数据较少，可以分别采用较为简单的插入排序。",
    continue: !1
  }, {
    string: "然后将这 5 组数据分别在内部进行排序。由于数据较少，可以分别采用较为简单的插入排序。",
    continue: !1
  }, {
    string: "然后将这 5 组数据分别在内部进行排序。由于数据较少，可以分别采用较为简单的插入排序。",
    continue: !1
  }, {
    string: "此时，5 组数据在内部都已经有序。到这里，我们完成了归并操作中的‘归’，即分解操作。",
    continue: !1
  }, {
    string: "下面我们开始‘并’操作，先将这 5 组数据合并成两组，1 2 归为一组，3 4 5 归为一组。",
    continue: !0
  }, {
    string: "我们先来合并 1 和 2。 这里我们使用标记插入法。 因为 1 和 2 在内部已经有序，我们用光标标记当前1 和 2 中最小的数字，进行比较。",
    continue: !1
  }, {
    string: "将较小的数字(1)插入到下方空序列中，并将光标向右移动，继续比较当前光标所指向的数字。",
    continue: !1
  }, {
    string: "4 小于 8， 将 4 插入到下方，位于 1 的右侧。 然后移动光标。",
    continue: !1
  }, {
    string: "继续比较，直到 1 和 2中的数字都被排序。",
    continue: !1
  }, {
    string: "继续比较，直到 1 和 2中的数字都被排序。",
    continue: !1
  }, {
    string: "此时我们已经把序列 1 和 2 中的数据合并成一个大的有序序列。",
    continue: !0
  }, {
    string: "同样的，我们用光标标记序列 3 4 5 中最小的数据。",
    continue: !1
  }, {
    string: "使用同样的标记插入插入方法，依次比较并插入下方的序列。",
    continue: !1
  }, {
    string: "使用同样的标记插入插入方法，依次比较并插入下方的序列。",
    continue: !1
  }, {
    string: "使用同样的标记插入插入方法，依次比较并插入下方的序列。",
    continue: !1
  }, {
    string: "使用同样的标记插入插入方法，依次比较并插入下方的序列。",
    continue: !1
  }, {
    string: "此时我们已经把序列 3 4 5 中的数据合并成一个大的有序序列。",
    continue: !1
  }, {
    string: "然后我们把合并后的两个大序列分别标记为 1 和 2。",
    continue: !1
  }, {
    string: "然后我们把合并后的两个大序列分别标记为 1 和 2。",
    continue: !1
  }, {
    string: "下面用同样的标记插入法合并 1 和 2。",
    continue: !0
  }, {
    string: "下面用同样的标记插入法合并 1 和 2。",
    continue: !1
  }, {
    string: "下面用同样的标记插入法合并 1 和 2。",
    continue: !1
  }, {
    string: "下面用同样的标记插入法合并 1 和 2。",
    continue: !1
  }, {
    string: "下面用同样的标记插入法合并 1 和 2。",
    continue: !1
  }, {
    string: "下面用同样的标记插入法合并 1 和 2。",
    continue: !1
  }, {
    string: "下面用同样的标记插入法合并 1 和 2。",
    continue: !1
  }, {
    string: "下面用同样的标记插入法合并 1 和 2。",
    continue: !1
  }, {
    string: "下面用同样的标记插入法合并 1 和 2。",
    continue: !1
  }, {
    string: "下面用同样的标记插入法合并 1 和 2。",
    continue: !1
  }, {
    string: "最终，我们合并了序列 1 和 2，从而完成了归并排序中的‘并’操作。",
    continue: !0
  }, {
    string: "到这里，我们完成了归并排序的演示。",
    continue: !1
  }],
  size: 37
}],
    o = [{
  Text: [{
    string: "线性查找又称顺序查找，是一种最简单的查找方法，它的基本思想是从第一个元素开始，逐个比较当前元素的数值，直到和查找的值相等。",
    continue: !0
  }, {
    string: "假设我们要在上方的序列中找数字7。现在我们从左侧开始逐一比较。",
    continue: !0
  }, {
    string: "第一个数字是8，不是我们要查找的数字，将光标向前移动。",
    continue: !1
  }, {
    string: "当前光标对应数字是 12, 依然不是我们要查找的数字，继续向前移动。",
    continue: !1
  }, {
    string: "继续向前，逐一比较，直到找到数字7。",
    continue: !1
  }, {
    string: "继续向前，逐一比较，直到找到数字7。",
    continue: !1
  }, {
    string: "继续向前，逐一比较，直到找到数字7。",
    continue: !1
  }, {
    string: "继续向前，逐一比较，直到找到数字7。",
    continue: !1
  }, {
    string: "继续向前，逐一比较，直到找到数字7。",
    continue: !1
  }, {
    string: "找到数字7，查找结束。",
    continue: !1
  }],
  size: 10
}],
    g = [{
  Text: [{
    string: "二分查找也称折半查找，它是一种效率较高的查找方法。但是它要求被查找的序列必须采用顺序存储结构，而且表中元素有序排列。",
    continue: !0
  }, {
    string: "每次查找取序列的中位数的值进行比较。如果目标值值大于中位数的值，则截取中位数右侧的序列再次进行二分查找，否则截取中位数左侧的序列再次进行二分查找，每经过一次比较，查找范围就缩小一半。",
    continue: !0
  }, {
    string: "假设我们查找的数是19。首先，我们找到当前序列中最小和最大的数。",
    continue: !1
  }, {
    string: "由于序列是有序的，中位数应该在序列的中间位置。我们找到中位数9，并与19进行比较。",
    continue: !1
  }, {
    string: "由于19大于9，所以下面我们要截取9右侧的序列继续进行二分查找。",
    continue: !0
  }, {
    string: "我们在新序列中重新定位最小和最大的数，最大数不变，最小数为9。",
    continue: !1
  }, {
    string: "然后找到中位数16。",
    continue: !1
  }, {
    string: "由于19大于16，所以下面我们要截取16右侧的序列继续进行二分查找。",
    continue: !0
  }, {
    string: "我们在新序列中重新定位最小和最大的数，最大数不变，最小数为16。",
    continue: !1
  }, {
    string: "然后在新序列中找到中位数。这样我们就找到了数字19。",
    continue: !1
  }, {
    string: "然后在新序列中找到中位数。这样我们就找到了数字19。",
    continue: !1
  }, {
    string: "如果使用线性查找，需要遍历10次才能找到数字19。 而对于二分查找来说，我们只用了三次折半操作就找到了数字19，效率比线性查找高出不少。",
    continue: !0
  }, {
    string: "到这里，我们完成了二分查找的演示。",
    continue: !1
  }],
  size: 13
}],
    c = [{
  Text: [{
    string: "插值查找，是对于二分查找的优化，当序列中元素数量比较多时，能够获得更好的查找效率。",
    continue: !0
  }, {
    string: "插值查找基于二分查找，将查找点的选择由中位数改进为根据查找数据与查找表中最大最小数据比较后的自适应选择。",
    continue: !0
  }, {
    string: "我们将二分查找中的mid = (low + high) / 2 改进为 mid = low + (high - low) * (key - a[low]) / (a[high] - a[low])。",
    continue: !0
  }, {
    string: "假设我们要找的数字是8。首先，我们找到序列中的最小和最大值。",
    continue: !1
  }, {
    string: "根据计算公式。mid = low + (high - low) * (key - a[low]) / (a[high] - a[low])，得到mid的值是2 （序列下标从0开始），对应数字是5。",
    continue: !1
  }, {
    string: "由于5小于8，所以下面我们要截取5右边的序列继续进行差值查找。",
    continue: !1
  }, {
    string: "我们在新序列中重新定位最小和最大的数，最大数不变，最小数为5。",
    continue: !1
  }, {
    string: "继续使用公式mid = low + (high - low) * (key - a[low]) / (a[high] - a[low])找到中位数，得到mid值是3，对应数字是8。",
    continue: !1
  }, {
    string: "这样我们就找到了数字8。",
    continue: !0
  }, {
    string: "我们如果仔细分析一下插值查找中使用的公式: mid = low + (high - low) * (key - a[low]) / (a[high] - a[low])。实际上，每一次在执行查找之前，它会根据要查找的值，当前序列的最小以及最大值动态调节下一个查找位置。",
    continue: !0
  }, {
    string: "这样的查找算法在被查找的序列数据量较大且数值分布比较均匀时是十分有效的。",
    continue: !1
  }],
  size: 11
}],
    a = [{
  Text: [{
    string: "数组是具有相同数据类型的有序元素序列。",
    continue: !0
  }, {
    string: "数组中的各元素的存储是有先后顺序的，它们在内存中按照这个先后顺序连续存放在一起。",
    continue: !0
  }, {
    string: "数组元素用数组的名字和它自己在数组中的顺序位置来表示。例如，a[0]表示名字为a的数组中的第一个元素，a[1]代表数组a的第二个元素，a[2]代表数组a的第三个元素，以此类推。",
    continue: !1
  }, {
    string: "这里a[0], a[1], a[2]中存储的数据分别是 A, C, D。",
    continue: !0
  }, {
    string: "数组中的数据存储在连续的内存中，因此只要知道数组起始地址，便可以方便地使用数组下标来访问各个元素。",
    continue: !1
  }, {
    string: "数组中的数据存储在连续的内存中，因此只要知道数组起始地址，便可以方便地使用数组下标来访问各个元素。",
    continue: !1
  }, {
    string: "数组中的数据存储在连续的内存中，因此只要知道数组起始地址，便可以方便地使用数组下标来访问各个元素。",
    continue: !1
  }, {
    string: "数组中的数据存储在连续的内存中，因此只要知道数组起始地址，便可以方便地使用数组下标来访问各个元素。",
    continue: !1
  }, {
    string: "数组的连续存储特性使得我们可以随机访问数据，但是同时也使得添加/删除数据的成本增加。",
    continue: !1
  }, {
    string: "假设我们要在数据 A 和 C 之间插入数据 B。",
    continue: !1
  }, {
    string: "因为数组中的数据是连续存储的，我们需要将 C 和 D 各向右移动一个位置，然后将 B 放入到 a[1]中。",
    continue: !1
  }, {
    string: "首先，在插入之前确保数组的末尾要有额外的的空间。",
    continue: !1
  }, {
    string: "先将 D 移动到 a[3]",
    continue: !1
  }, {
    string: "再将 C 移动到 a[2]",
    continue: !1
  }, {
    string: "最后将 B 移动到 a[1]",
    continue: !1
  }, {
    string: "这样我们就成功地插入了数据 B。",
    continue: !0
  }, {
    string: "现在，我们尝试删除a[0]中的数据。",
    continue: !0
  }, {
    string: "首先，我们找到a[0]中的数据A并删除。",
    continue: !1
  }, {
    string: "数组需要保持连续存储特性，我们将后续的数据(B)搬移到a[0]中。",
    continue: !1
  }, {
    string: "再将数据 C 搬移到a[1]中。",
    continue: !1
  }, {
    string: "再将数据 D 搬移到a[2]中。",
    continue: !1
  }, {
    string: "最后删除多余的空间a[3]，这样就完成了一次删除操作。",
    continue: !1
  }, {
    string: "我们看到，数组的连续存储特性使得对于数组中数据的访问和遍历非常快速高效，但是对于插入和删除来说，由于存在数据搬移，操作成本较高。",
    continue: !0
  }, {
    string: "到这里，我们完成了数组的演示。",
    continue: !1
  }],
  size: 24
}],
    A = [{
  Text: [{
    string: "链表是一种在内存上非连续、非顺序的存储结构，数据元素的逻辑顺序是通过链表中的指针链接实现的。",
    continue: !0
  }, {
    string: "如图所示, A, B, C三个数据在内存上不连续。现在我们把A, B, C三个数据用链表指针链接, 这样就组成了一个链表。",
    continue: !1
  }, {
    string: "链表中数据的访问要通过顺序读取, 从头部开始。",
    continue: !0
  }, {
    string: "链表的每个元素中都包含了指向下一个元素的地址的指针。这里 A 包含了指向 B 的指针，B 包含了指向 C 的指针。",
    continue: !0
  }, {
    string: "相对数组而言，链表添加/删除数据要简单一些。只要改变添加/删除元素的相邻元素的指针即可。",
    continue: !0
  }, {
    string: "如果要删除链表中的一个元素，先直接删除该元素，然后将指向该元素的指针直接指向该元素后面的元素。",
    continue: !0
  }, {
    string: "假设我们要删除链表中的 B 元素，则先删除B, 然后再将 A 指向 C。",
    continue: !0
  }, {
    string: "假设我们要删除链表中的 B 元素，则先删除B, 然后再将 A 指向 C。",
    continue: !1
  }, {
    string: "假设我们要删除链表中的 B 元素，则先删除B, 然后再将 A 指向 C。",
    continue: !1
  }, {
    string: "这就成功删除了元素 B。",
    continue: !0
  }, {
    string: "现在我们重新将 B 还原。",
    continue: !0
  }, {
    string: "现在我们重新将 B 还原。",
    continue: !1
  }, {
    string: "类似的，如果我们要插入一个元素，则改变改元素两侧指针的指向即可。",
    continue: !0
  }, {
    string: "假设我们要在 B 和 C 之间添加 D。",
    continue: !1
  }, {
    string: "我们先将B 指向 D。",
    continue: !1
  }, {
    string: "我们先将B 指向 D。",
    continue: !1
  }, {
    string: "然后再将 D 指向 C。",
    continue: !1
  }, {
    string: "这样我们就成功添加了元素 D。",
    continue: !0
  }, {
    string: "到这里，我们完成了链表的演示。",
    continue: !1
  }],
  size: 19
}],
    l = [{
  Text: [{
    string: "栈是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶，相对地，把另一端称为栈底。栈是一种后进先出(LIFO)的数据结构，后入栈的元素先出栈，而先入栈的元素要等它后面的元素出栈之后才能出栈。",
    continue: !0
  }, {
    string: "开始时，栈里面没有数据。这是一个空的栈，栈底指针指向栈底最底部。",
    continue: !0
  }, {
    string: "每次入栈一个元素栈底不变，而栈顶会随着入栈的元素增加而增长。",
    continue: !1
  }, {
    string: "每次入栈一个元素栈底不变，而栈顶会随着入栈的元素增加而增长。",
    continue: !1
  }, {
    string: "分别点击入栈和出栈按钮进行栈的操作。 ",
    continue: !1
  }],
  size: 5
}],
    B = [{
  Text: [{
    string: "队列是一种特殊的线性表，特殊之处在于它只允许在表的前端进行出队（Dequeue）操作，而在表的后端行入队(Enqueue)操作。队列是一种先进先出(FIFO)的数据结构。进行插入操作的端称为队尾，进行删除操作的端称为队头。",
    continue: !0
  }, {
    string: "分别点击入队和出队按钮进行队列的操作。 ",
    continue: !1
  }],
  size: 2
}],
    C = [{
  Text: [{
    string: "二叉树是树的一种。在进一步了解二叉树之前，我们先来了解一些树的基本概念。",
    continue: !1
  }, {
    string: "数有且仅有一个特定的称为根（Root）的结点。在这里，A是根结点。",
    continue: !1
  }, {
    string: "结点拥有的子树数目称为结点的度。上图节点中的数字代表了各个节点的度。",
    continue: !1
  }, {
    string: "每个节点（除了根）都恰好有一条边向上连接到另一个节点，上面的节点就称为下面节点的父节点。上图中 A 是 B, C的父节点。B, C是 A 的子节点。",
    continue: !1
  }, {
    string: "具有同一个父点的子结点之间互称兄弟结点。这里 B 和 C, D 和 E都是兄弟节点。",
    continue: !0
  }, {
    string: "一棵树可以有多层。从根开始定义起，根为第一层，根的子节点为第二层，以此类推。",
    continue: !1
  }, {
    string: "树中结点的最大层次数称为树的深度或高度。上图中这棵树的高度为3",
    continue: !0
  }, {
    string: "二叉树是一种特殊的树，它是n(n>=0)个结点的有限集合，该集合或者为空集（称为空二叉树），或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。上图就是一颗普通的二叉树。",
    continue: !0
  }, {
    string: "由二叉树定义以及图示分析得出二叉树有以下特点：1）每个结点最多有两颗子树，所以二叉树中不存在度大于2的结点。2）左子树和右子树是有顺序的，次序不能任意颠倒。3）即使树中某结点只有一棵子树，也要区分它是左子树还是右子树。",
    continue: !0
  }, {
    string: "二叉树还具有这些性质: 1）在二叉树的第i层上最多有2^(i-1) 个节点 。（i>=1）2）二叉树中如果深度为k,那么最多有(2^k)-1个节点。(k>=1）。(3) 对于任意一棵二叉树，如果其叶结点数为N0，而度数为2的结点总数为N2，则N0=N2+1；",
    continue: !1
  }, {
    string: "到这里，我们完成了二叉树的演示。",
    continue: !1
  }],
  size: 11
}],
    S = [{
  Text: [{
    string: "二叉搜索树是一种节点值之间满足一定大小关系的二叉树。",
    continue: !0
  }, {
    string: "它必须满足三个条件: 1.左子树中每个节点的值都小于于该节点值。2.右子树中每个节点的值都大于该节点值。3，左右子树也是二叉搜索树。",
    continue: !0
  }, {
    string: "下面我们就来构造一棵二叉搜索树。",
    continue: !0
  }, {
    string: "可以看到，对于二叉搜索树的每一个节点，左子节点的值都更小，右子节点的值都更大。",
    continue: !1
  }, {
    string: "二叉搜索树的这个特性使它非常适合进行快速查找。",
    continue: !0
  }, {
    string: "在二叉搜索树进行查找某个节点的方法是：从根节点开始，如果要查找的值小于该节点，就从左子树继续进行查找，反之则从右子树继续进行查找。",
    continue: !0
  }, {
    string: "假设我们要查找节点 4，首先选中根节点。",
    continue: !1
  }, {
    string: "由于要查找的节点 4 小于根节点 8， 我们选中根节点的左子节点 3 继续查找。",
    continue: !1
  }, {
    string: "由于要查找的节点 4 大于节点 3， 我们选中它的右子节点 6 继续查找。",
    continue: !1
  }, {
    string: "由于要查找的节点 4 小于节点 6， 我们选中它的左子节点，这样就找到了节点 4。",
    continue: !1
  }, {
    string: "下面我们来看一下如何在二叉搜索树中插入节点。",
    continue: !1
  }, {
    string: "在二叉搜索树中新插入的节点，必须添加在叶子节点中。",
    continue: !0
  }, {
    string: "假设我们要插入新节点 21。和查找类似，我们从根节点开始逐一遍历。",
    continue: !1
  }, {
    string: "由于要插入的节点 21 大于根节点 8， 我们选中根节点的右子节点 10 继续遍历。",
    continue: !1
  }, {
    string: "由于要插入的节点 21 大于节点 10， 我们选中它的右子节点 14 继续遍历。",
    continue: !1
  }, {
    string: "由于要插入的节点 21 大于节点 14， 而节点 14 的右子节点为空，此时我们将节点 21 添加为 14 的右子节点。",
    continue: !1
  }, {
    string: "这就完成了二叉搜索树的插入操作。",
    continue: !1
  }, {
    string: "下面我们来看一下如何在二叉搜索树中删除节点。",
    continue: !0
  }, {
    string: "在二叉搜索树中删除节点分为三种情况：1.删除的节点没有子节点。2.删除的节点有一个子节点。3.删除的节点有两个子节点。",
    continue: !0
  }, {
    string: "先开看第一种情况，删除的节点没有子节点，即待删除的节点是叶子节点。此时我们直接将它删除就可以。不需要其它操作。",
    continue: !0
  }, {
    string: "假设我们要删除的节点是 13。它没有子节点，现在我们直接将它从二叉搜索树中删掉。",
    continue: !1
  }, {
    string: "假设我们要删除的节点是 13。它没有子节点，现在我们直接将它从二叉搜索树中删掉。",
    continue: !1
  }, {
    string: "下面来看第二种情况，待删除节点只有一个子节点。我们用它的子节点的值替换当前的值，然后将它的子节点删除。",
    continue: !0
  }, {
    string: "假设我们要删除的节点是 14。它的子节点是 21，现在我们用 21 替换 14， 再将子节点 21 删除。",
    continue: !1
  }, {
    string: "假设我们要删除的节点是 14。它的子节点是 21，现在我们用 21 替换 14， 再将子节点 21 删除。",
    continue: !1
  }, {
    string: "假设我们要删除的节点是 14。它的子节点是 21，现在我们用 21 替换 14， 再将子节点 21 删除。",
    continue: !1
  }, {
    string: "假设我们要删除的节点是 14。它的子节点是 21，现在我们用 21 替换 14， 再将子节点 21 删除。",
    continue: !1
  }, {
    string: "我们再来看第三种情况，待删除节点有两个子节点。我们用它的右子树中最小的的值替换当前的值，然后将这个最小子节点删除。",
    continue: !1
  }, {
    string: "假设我们要删除的节点是 3。它的右子树中最小节点是 4，现在我们用 4 替换 3， 再将叶子节点 3 删除。",
    continue: !1
  }, {
    string: "假设我们要删除的节点是 3。它的右子树中最小节点是 4，现在我们用 4 替换 3， 再将叶子节点 3 删除。",
    continue: !1
  }, {
    string: "假设我们要删除的节点是 3。它的右子树中最小节点是 4，现在我们用 4 替换 3， 再将叶子节点 3 删除。",
    continue: !1
  }, {
    string: "假设我们要删除的节点是 3。它的右子树中最小节点是 4，现在我们用 4 替换 3， 再将叶子节点 3 删除。",
    continue: !1
  }, {
    string: "这就完成了二叉搜索树的删除操作。",
    continue: !1
  }, {
    string: "到这里，我们完成了二叉搜索树的演示。",
    continue: !1
  }],
  size: 34
}],
    D = [{
  Text: [{
    string: "完全二叉树的定义：若设二叉树的深度为k，除第k层外，其他各层（1～（k-1）层）的节点数都达到最大值，且第k层所有的节点都连续集中在最左边，这棵树就是完全二叉树。上图就是一颗完全二叉树。",
    continue: !1
  }, {
    string: "完全二叉树是一种常用数据结构，而例如堆就是是一种完全二叉树或者近似完全二叉树，像常用的堆排序算法，Dijkstra算法，Prim算法等都要用堆这种数据结构。",
    continue: !0
  }, {
    string: "下面我们看一些具体的例子来判断一棵树是否是完全二叉树。",
    continue: !0
  }, {
    string: "上面的这棵树第三层的节点数没有达到最大值，不满足 ‘若设二叉树的深度为k，除第k层外，其他各层（1～（k-1）层）的节点数都达到最大值’ 这个条件，所以不是一颗完全二叉树。",
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: "上面的这棵树，第四层的节点没有集中在最左边，不满足 ‘若设二叉树的深度为k，第k层所有的节点都连续集中在最左边’ 这个条件，所以不是一颗完全二叉树。",
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: "这棵树满足所有完全二叉树的条件，是一颗完全二叉树。",
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: "到这里，我们完成了完全二叉树的演示。",
    continue: !1
  }],
  size: 10
}],
    I = [{
  Text: [{
    string: "堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质: 1)堆总是一棵完全二叉树。 2)堆中某个节点的值总是不大于或不小于其父节点的值。",
    continue: !0
  }, {
    string: "堆通常可以分为大顶堆和小顶堆。",
    continue: !0
  }, {
    string: "小顶堆中每个节点的值总是小于等于其左右孩子的值。上面就是一个小顶堆。",
    continue: !1
  }, {
    string: "大顶堆中每个节点的值总是大于等于其左右孩子的值。上面就是一个大顶堆。",
    continue: !1
  }, {
    string: "下面我们试着在这个大顶堆中插入两个数据。",
    continue: !1
  }, {
    string: "插入其实就是把插入结点放在堆最后面，然后与父亲比较，如果父亲值小于它，那么它就和父亲结点交换位置，重复该过程，直到插入节点遇到一个值比它大的父亲或者它成为树根结点。",
    continue: !0
  }, {
    string: "插入数据 2，由于 2 小于它顶父节点 3，我们不需要再做其它操作。",
    continue: !1
  }, {
    string: "再插入数据 6， 由于它大于父节点 3， 因此需要将它和 3 交换位置。",
    continue: !1
  }, {
    string: "经过交换之后， 6 小于它顶父节点 7，满足了大顶堆堆条件。",
    continue: !1
  }, {
    string: "下面我们来看如何从堆中删除数据。",
    continue: !0
  }, {
    string: "删除就是删除大顶堆中的最大值或者小顶堆中的最小值，也就是树根。",
    continue: !1
  }, {
    string: "删除根节点之后，把位于最右下角的一个结点当成新的树根。",
    continue: !1
  }, {
    string: "然后与它左右子节点比较，与值最大并且值大于它的子节点进行交换，直到它的子节点都是小于它。",
    continue: !1
  }, {
    string: "下面我们再删除一个数据。",
    continue: !0
  }, {
    string: "这是堆顶顶数据是 6， 我们删除它。",
    continue: !1
  }, {
    string: "然后将堆右下角的数据 2 移动到堆顶。",
    continue: !1
  }, {
    string: "比较 2 和它的左右子节点，与较大的 5 进行交换。",
    continue: !1
  }, {
    string: "此时 2 的右子节点 4 仍然大于 2， 因此继续将 2 与 4 进行交换。",
    continue: !1
  }, {
    string: "现在这颗树满足了堆堆条件。我们完成了数据的删除。",
    continue: !0
  }, {
    string: "到这里，我们完成了堆的演示。",
    continue: !1
  }],
  size: 20
}],
    L = [{
  Text: [{
    string: "一个图就是一些顶点的集合，这些顶点通过一系列边连接。",
    continue: !0
  }, {
    string: "顶点用圆圈表示，边就是这些圆圈之间的连线。顶点之间通过边连接。",
    continue: !0
  }, {
    string: "图可以表示事物之间的联系，上图表示的是城市之间的航班路线，节点之间有边连接则表示这两个城市之间有航班通行。",
    continue: !1
  }, {
    string: "图分为有向图和无向图，如果每一条边都是没有方向的，则这个图就是无向图。上图就是一个无向图。",
    continue: !0
  }, {
    string: "如果每条边都是有方向的，则这张图就是有向图。如图所示，现在我们将每条边都标注了方向。箭头方向代表航班的飞行方向，可以看到，上海有航班飞到东京，但从东京不能飞到上海。",
    continue: !1
  }, {
    string: "边可以有权重（weight），即每一条边会被分配一个数值。考虑一个代表航线的图。那么边的权重可以是飞行时间，或者机票价格。",
    continue: !1
  }, {
    string: "到这里，我们完成了图的演示。",
    continue: !1
  }],
  size: 7
}],
    x = [{
  Text: [{
    string: "一个图的基本组成就是节点和边，因此要表示一张图，我们要找到一种描述节点之间边关系的数据结构。",
    continue: !0
  }, {
    string: "这里我们来演示两种常用的表示图的数据结构。分别是邻接矩阵和邻接列表。",
    continue: !0
  }, {
    string: "首先来看邻接矩阵如何表示无向图。图中4个节点对应矩阵中的4行4列。我们把这个矩阵命名为a，则a[i][j]的值代表着i节点与j节点之间是否存在着边，0表示两点之间无连接。1表示两点之间有边连接。",
    continue: !1
  }, {
    string: "对于无向图来说，则 a[i][j] 与 a[j][i] 表示的值是一样的。因为假如 1 和 2 节点之间有边相连，那么反过来 2 和 1 之间也是一样。",
    continue: !0
  }, {
    string: "类似的，邻接矩阵也可以表示有向图。",
    continue: !1
  }, {
    string: "对于有向图来说，因此时的 a[i][j] 表示的是存在i节点指向j节点的边，而j节点是否有指向i节点的边就不一定了。",
    continue: !1
  }, {
    string: "再来看邻接列表，与邻接矩阵的不同之处就在于，邻接矩阵把所有点与点之间的关系是否存在都表示出来了，而邻接矩阵只把存在关系的点表示出来，没有表示则表明不存在着边关系。",
    continue: !1
  }, {
    string: "当然，邻接列表表示法也可以用来表示有向图。",
    continue: !1
  }, {
    string: "这里的邻接列表表示0节点有指向1节点的一条边，1节点有一条指向2节点的边，2节点有指向3节点的一条边，3节点有指向1节点的一条边，即此时表示的边关系都是带有方向的。",
    continue: !1
  }, {
    string: "邻接表相比于邻接矩阵来说，所占用的空间更小，这是邻接表的一个优势。但是如果表示的是一个有很多条边的图, 即稠密图的话, 考虑到邻接表中要附加用于表述连接关系的指针，这时采用邻接矩阵表示法会更好。",
    continue: !0
  }, {
    string: "一般邻接表适合表示稀疏图，邻接矩阵适合表示稠密图。",
    continue: !0
  }, {
    string: "到这里，我们完成了图的表示方法的演示。",
    continue: !1
  }],
  size: 12
}],
    E = [{
  Text: [{
    string: "图的深度优先搜索是指，选定一个出发点后进行遍历，如果有邻接的未被访问过的节点则继续前进。若不能继续前进，则回退一步再前进，若回退一步仍然不能前进，则连续回退至可以前进的位置为止。重复此过程，直到所有的顶点都被遍历。",
    continue: !0
  }, {
    string: "深度优先搜索是递归过程，带有回退操作，因此可以使用栈存储访问的路径信息。当访问到的当前顶点没有可以前进的邻接顶点时，需要进行出栈操作，将当前位置回退至出栈元素位置。",
    continue: !0
  }, {
    string: "我们以上图中所示的无向图来说明深度优先遍历过程。",
    continue: !0
  }, {
    string: "我们创建一个栈，用于演示深度优先遍历的过程。",
    continue: !1
  }, {
    string: "假设从节点 A 开始遍历。首先选取节点 A 为起始点，将A入栈，并标记 A 为已访问节点。",
    continue: !1
  }, {
    string: "假设从节点 A 开始遍历。首先选取节点 A 为起始点，将A入栈，并标记 A 为已访问节点。",
    continue: !1
  }, {
    string: "A的邻接顶点有B、D、C，从中任意选取一个结点前进。这里我们选取 D 节点为前进位置顶点。将 D 入栈，并标记 D 为已访问节点。",
    continue: !1
  }, {
    string: "A的邻接顶点有B、D、C，从中任意选取一个结点前进。这里我们选取 D 节点为前进位置顶点。将 D 入栈，并标记 D 为已访问节点。",
    continue: !1
  }, {
    string: "顶点 D 的邻接节点有A、E 和 B，此时 A 已经标记为已访问节点，因此不能继续访问。从 E 或者 B 中选取一个节点前进，这里我们选取 E 为前进位置节点。将 E 入栈，标记 E 为已访问节点。",
    continue: !1
  }, {
    string: "顶点 D 的邻接节点有A、E 和 B，此时 A 已经标记为已访问节点，因此不能继续访问。从 E 或者 B 中选取一个节点前进，这里我们选取 E 为前进位置节点。将 E 入栈，标记 E 为已访问节点。",
    continue: !1
  }, {
    string: "顶点 E 的邻接节点只有D、G，D 已被标记，不能继续访问，因此选取 G 为前进位置节点，将 G 入栈，标记 G 为已访问节点。",
    continue: !1
  }, {
    string: "顶点 E 的邻接节点只有D、G，D 已被标记，不能继续访问，因此选取 G 为前进位置节点，将 G 入栈，标记 G 为已访问节点。",
    continue: !1
  }, {
    string: "节点 G 的邻接节点均已被标记，此时无法继续前进，则需要进行回退。将当前位置回退至节点 E，回退的同时将 G 出栈。",
    continue: !0
  }, {
    string: "节点 G 的邻接节点均已被标记，此时无法继续前进，则需要进行回退。将当前位置回退至节点 E，回退的同时将 G 出栈。",
    continue: !1
  }, {
    string: "节点 E 的邻接节点也均被标记，需要继续回退，当前位置回退至 D，回退同时将 E 出栈。",
    continue: !0
  }, {
    string: "节点 E 的邻接节点也均被标记，需要继续回退，当前位置回退至 D，回退同时将 E 出栈。",
    continue: !1
  }, {
    string: "节点 D 可以前进的节点位置为 B，将 B 入栈，并标记 B 顶点。当前位置指向节点 B。",
    continue: !1
  }, {
    string: "节点 D 可以前进的节点位置为 B，将 B 入栈，并标记 B 顶点。当前位置指向节点 B。",
    continue: !1
  }, {
    string: "节点 B 的邻接节点均已被标记，此时无法继续前进，则需要进行回退。将当前位置回退至节点 D，回退的同时将 B 出栈。",
    continue: !0
  }, {
    string: "节点 B 的邻接节点均已被标记，此时无法继续前进，则需要进行回退。将当前位置回退至节点 D，回退的同时将 B 出栈。",
    continue: !1
  }, {
    string: "节点 D 的邻接节点也均被标记，需要继续回退，当前位置回退至 A，回退同时将 B 出栈。",
    continue: !0
  }, {
    string: "节点 D 的邻接节点也均被标记，需要继续回退，当前位置回退至 A，回退同时将 B 出栈。",
    continue: !1
  }, {
    string: "节点 A 可以前进的节点位置为 C，将 C 入栈，并标记 C 节点。当前位置指向节点 C。",
    continue: !1
  }, {
    string: "节点 A 可以前进的节点位置为 C，将 C 入栈，并标记 C 节点。当前位置指向节点 C。",
    continue: !1
  }, {
    string: "节点 C 可以前进的节点位置为 F，将 F 入栈，并标记 F 节点。当前位置指向节点 F。",
    continue: !1
  }, {
    string: "节点 C 可以前进的节点位置为 F，将 F 入栈，并标记 F 节点。当前位置指向节点 F。",
    continue: !1
  }, {
    string: "节点 F 的邻接节点均已被标记，此时无法继续前进，则需要进行回退。将当前位置回退至节点 C，回退的同时将 F 出栈。",
    continue: !0
  }, {
    string: "节点 F 的邻接节点均已被标记，此时无法继续前进，则需要进行回退。将当前位置回退至节点 C，回退的同时将 F 出栈。",
    continue: !1
  }, {
    string: "节点 C 的邻接节点均已被标记，此时无法继续前进，则需要进行回退。将当前位置回退至节点 A，回退的同时将 C 出栈。",
    continue: !0
  }, {
    string: "节点 C 的邻接节点均已被标记，此时无法继续前进，则需要进行回退。将当前位置回退至节点 A，回退的同时将 C 出栈。",
    continue: !1
  }, {
    string: "此时 A 没有前进节点位置，继续回退，栈为空，则以A为起始的遍历结束。",
    continue: !0
  }, {
    string: "此时 A 没有前进节点位置，继续回退，栈为空，则以A为起始的遍历结束。",
    continue: !1
  }, {
    string: "到这里，我们完成了图的深度优先遍历的的演示。",
    continue: !1
  }],
  size: 33
}],
    T = [{
  Text: [{
    string: "图的广度优先遍历是按照一种由近及远的方式访问图的节点。在进行广度优先搜索时我们可以使用队列这种数据结构存储节点信息。",
    continue: !0
  }, {
    string: "广度优先搜索的思想是，先被访问的节点的邻接节点先于后被访问的顶点的邻接点被访问。按此要求遍历节点，直至图中所有已被访问的节点的邻接节点都被访问到。",
    continue: !0
  }, {
    string: "我们以上图中所示的无向图来说明广度优先遍历过程。",
    continue: !0
  }, {
    string: "我们创建一个队列，用于演示广度优先遍历的过程。",
    continue: !1
  }, {
    string: "选取A为起始点，A入队列，标记A，当前位置指向A。",
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: "队列头为A，A出队列。A的邻接顶点有C、D，将 C 和 D 入队，并标记 C、D。当前位置指向 C。",
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: "队列头为 C，C 出队列。C 的邻接节点有F 和 B, 将 F 和 B 入队列，并标记 F, B。当前位置指向 D。",
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: "队列头为 D，D 出队列。D 的邻接顶点有 B 和 E, 其中 B 已经被标记。 将 E 入队, 并标记 E。当前位置指向 F。",
    continue: !0
  }, {
    string: null,
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: "队列头为 F, F 的邻接节点只有 C, C 已被标记，没有元素入栈。将 F 出队。当前位置指向 B。",
    continue: !0
  }, {
    string: null,
    continue: !1
  }, {
    string: "队列头为 B, B 的邻接节点有 C 和 D, C 和 D 均已被标记，没有元素入栈。将 B 出队。当前位置指向 E。",
    continue: !0
  }, {
    string: null,
    continue: !1
  }, {
    string: "队列头为 E, E 出队, E 的邻接节点只有 G, 将 G 入队。并标记 G。当前位置指向 G。",
    continue: !0
  }, {
    string: null,
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: "队列头为 G, G 的邻接节点只有 E, E 已被标记，没有元素入栈。将 G 出队。",
    continue: "true"
  }, {
    string: null,
    continue: !1
  }, {
    string: "此时队列空，以 A 为起始点的遍历结束。",
    continue: !0
  }, {
    string: "到这里，我们完成了图的广度优先遍历的的演示。",
    continue: !1
  }],
  size: 32
}],
    z = [{
  Text: [{
    string: "游程编码是一种比较简单的数据压缩算法，其基本思想是将重复且连续出现多次的字符使用 [连续出现次数，某个字符] 这样的组合来描述。",
    continue: !0
  }, {
    string: "游程编码是一种无损的数据压缩算法，对于连续性好的数据的压���性能较高，适用于二值图像。",
    continue: !0
  }, {
    string: "如上图所示，这是一张5x5的图片，由黑白两种像素组成，分别用1 和 0 来表示。",
    continue: !1
  }, {
    string: "我们可以用二进制数值来表示这张图: 1 1 1 1 0 0 0 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1 1 0 0 。",
    continue: !0
  }, {
    string: "下面我们试着用游程编码来压缩数据。",
    continue: !0
  }, {
    string: "对于 1 1 1 1 可以编码为 4 1，代表连续的 4 个 1。",
    continue: !1
  }, {
    string: "对于 0 0 0 可以编码为 3 0，代表连续的 3 个 0。",
    continue: !1
  }, {
    string: "对于 1 1 1 1 1 1 1  可以编码为 7 1，代表连续的 7 个 1。",
    continue: !1
  }, {
    string: "对于单独的一个 0， 可以编码为 1 0。",
    continue: !1
  }, {
    string: "对于单独的一个 1， 可以编码为 1 1。",
    continue: !1
  }, {
    string: "对于单独的一个 0， 可以编码为 1 0。",
    continue: !1
  }, {
    string: "对于 1 1 1 1 1 1   可以编码为 6 1，代表连续的6 个 1。",
    continue: !1
  }, {
    string: "对于 0 0   可以编码为 2 0，代表连续的 2 个 0。",
    continue: !1
  }, {
    string: "经过游程编码，25 个数据被压缩至 16 个数据。",
    continue: !1
  }, {
    string: "在此基础上，我们是否可以进一步进行压缩呢？。",
    continue: !0
  }, {
    string: "观察编码数据我们可以发现，它是由[x 1], [y 0] 这样的组合交替组成的, x, y 分别代表1 和 0 出现的次数。",
    continue: !1
  }, {
    string: "由于 1 和 0 必然是交替出现的，即连续的 1 之后必然是连续的 0， 反之亦然。因此在编码时，我们只要标注第一次出现的数字是 1 还是 0 即可。",
    continue: !0
  }, {
    string: "上面的序列第一个出现的数字是 1， 我们将它编码为 14, 剩下的数字只要记录出现的次数即可。这样我们在解码时，只要知道前两位代表了4 个 1， 之后的数字为 0 和 1 交替出现的次数，就可以快速恢复原数据。",
    continue: !1
  }, {
    string: "通过这样的操作，我们将编码后的数据进一步压缩至 9 个，数据量只有原始数据的 36%。",
    continue: !0
  }, {
    string: "到这里，我们完成了游程编码的演示。",
    continue: !1
  }],
  size: 20
}],
    h = [{
  Text: [{
    string: "霍夫曼编码(Huffman Coding)，是一种编码方式，属于可变字长编码(VLC)的一种。",
    continue: !0
  }, {
    string: "霍夫曼编码是无前缀编码，即解码时不需要额外的前缀信息。它主要根据字符出现的频率来最大化节省编码的存储空间。",
    continue: !0
  }, {
    string: "假设我们要对上面的这个字符串进行霍夫曼编码。",
    continue: !1
  }, {
    string: "首先，我们来看一下未未编码之前，这些字符是如何在计算机中存储的。",
    continue: !0
  }, {
    string: "在计算机中，我们通常使用ASCII码来表示各种字符。每个ASCII码由 8 比特组成。这里我们只用到了字符A 到 E，它们的ASCII码如上所示。",
    continue: !1
  }, {
    string: "由于我们的字符串中只出现了A 到 E这5个字符，为了尽可能的压缩数据，我们并不需要用8比特来表示5个字符。",
    continue: !0
  }, {
    string: "实际上，我们用三个比特就可以表示5种不同的字符。",
    continue: !1
  }, {
    string: "这里，可能我们会想，也许可以去掉一些冗余的比特，来进一步进行压缩。比如 A, B, C, D前面的 0。",
    continue: !0
  }, {
    string: null,
    continue: !1
  }, {
    string: "但这时，虽然A 到 E分别由不同的比特表示，但是组合起来的编码在解码时会有歧义。 例如 1101 既可以表示 DAB, 也可以表示 BCB。因此解码时需要额外的信息才能消除歧义。这样的编码不是无前缀编码。",
    continue: !0
  }, {
    string: "什么是无前缀编码呢？",
    continue: !0
  }, {
    string: "举例来说，上面的组合就是一组无前缀编码。即无论如何排列组合，解码时都不会产生歧义。",
    continue: !1
  }, {
    string: "此时我们再来观察一下上面这组字符中A 到 E出现的次数。",
    continue: !0
  }, {
    string: "我们看到 D 和 E 出现的次数最多，但是却用了更长的比特来表示（3位）。A, B, C出现的次数少，却用了较少的比特来表示（2位）。这是不合理的。",
    continue: !1
  }, {
    string: "为了获得更大的压缩效率，我们可以将 A 和 E, B 和 D的表示方法交换。",
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: "此时我们的编码方式获得了最大的效率，即用最少的比特数来表示所有字符，且解码时不产生歧义，这就是霍夫曼编码的目标。",
    continue: !1
  }, {
    string: "为了实现目标，我们使用霍夫曼树来完成编码过程。下面我们从头开始演示如何将字符串编码。",
    continue: !1
  }, {
    string: "这里 A,B,C,D,E出现的频率分别为1,2,3,4,5,我们将出现的频率称作权值。第一步先取两个最小权值作为左右子树构造一个新树，即取1，2构成新树，其结点为1+2=3。",
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: "第二步再把新生成的权值为3的结点放到剩下的集合中，所以集合变成{3,3,4,5}。",
    continue: !1
  }, {
    string: "第三步再取最小的权值3，3构成新树6，所以集合变成{4,5,6}，取最小的两个权值构成新树。",
    continue: !1
  }, {
    string: "继续重复上述步骤，每次取集合中两个最小权值构成新树，再并入新的集合。",
    continue: !1
  }, {
    string: null,
    continue: !1
  }, {
    string: "到这里我们根据权值生成了一颗完整的霍夫曼树。",
    continue: !1
  }, {
    string: "然后我们将这颗树所有的左侧的边标记为0，右侧的边标记为1。",
    continue: !1
  }, {
    string: "根节点开始到每个字符节点所经过的边，就是每个字符所对应的霍夫曼编码的值。",
    continue: !0
  }, {
    string: "A对应的编码是 010。",
    continue: !1
  }, {
    string: "B对应的编码是 011。",
    continue: !1
  }, {
    string: "C对应的编码是 00。",
    continue: !1
  }, {
    string: "D对应的编码是 10。",
    continue: !1
  }, {
    string: "E对应的编码是 11。",
    continue: !1
  }, {
    string: "通过霍夫曼树生成的编码既保证了解码时不会产生歧义（无前缀编码），又根据字符出现的频率最大化的节省了存储空间。",
    continue: !1
  }, {
    string: "到这里，我们完成了霍夫曼编码的演示。",
    continue: !1
  }],
  size: 40
}],
    F = [{
  Text: [{
    string: "哈希表又叫散列表，是一组以 Key 和 Value 组成的表。",
    continue: !0
  }, {
    string: "这里的 Key 和 Value 存在某种映射关系，通过这种映射关系，我们可以方便地通过 Key 来找到 Value。我们把这个映射关系称为哈希函数。",
    continue: !1
  }, {
    string: "哈希表具有非常高的查询效率，因为可以直接通过哈希函数找到 Key 所对应的值，而不需要向传统的查找算法一样进行遍历和比较。哈希查找的算法复杂度是O(1)。",
    continue: !0
  }, {
    string: "下面我们来看一下哈希表是如何实现的。",
    continue: !0
  }, {
    string: "这张哈希表代表超市里水果编号，Key 代表了水果的名称， Value代表了水果的编号 。",
    continue: !1
  }, {
    string: "那么我们是怎么通过水果的名称得到它的编号的呢？换句话说，这里的哈希函数是什么呢？",
    continue: !0
  }, {
    string: "实际上这里使用的哈希函数非常简单。我们将字母 A ~ Z(不区分大小写) 分别用数字 1 ~ 26 来表示。 Value 的值等于 Key中所有字母相加。 例如 Apple的 值为 1 + 16 + 16 + 12 + 5 = 50。",
    continue: !1
  }, {
    string: "同样的，通过哈希函数可以得到 Orange, Cherry, Pecan的值。",
    continue: !1
  }, {
    string: "哈希函数是构成哈希表的关键。通过哈希函数我们可以构建 Key -> Value的组合。从而实现快速查找。",
    continue: !1
  }, {
    string: "哈希函数还有一个特点，就是不同的 Key 可能会生成相同的 Value。这就是哈希冲突。",
    continue: !0
  }, {
    string: "对于这里我们使用的哈希算法来说，Apple和 Buza 通过哈希函数计算出的 Value 都是50。这样的话，Apple 和 Buza 的编号产生了冲突，这个时候通过仅仅哈希函数无法确定 Key 的位置。",
    continue: !1
  }, {
    string: "那么如何解决哈希冲突呢？",
    continue: !0
  }, {
    string: "一种比较常见的方法是：链地址法。在构建哈希表时，对于具有相同 Value 的数据，采用一个链表依次记录每一个Key值。",
    continue: !1
  }, {
    string: "这样的话，当我们计算出Value之后，再遍历其所指向的链表，就可以确定 Key 的位置。",
    continue: !0
  }, {
    string: "到这里，我们完成了对哈希表的演示。",
    continue: !1
  }],
  size: 15
}],
    k = [{
  Text: [{
    string: "Kruskal（克鲁斯卡尔）算法是一种用来查找最小生成树的算法。由Joseph Kruskal在1956年发表。它是贪婪算法的一种典型应用。",
    continue: !0
  }, {
    string: "在解释Kruskal算法之前，我们我们先来了解一下最小生成树的定义。",
    continue: !0
  }, {
    string: "在一给定的无向图G = (V, E) 中，(u, v) 代表连接顶点 u 与顶点 v 的边（即），而 w(u, v) 代表此边的权重，若存在 T 为 E 的子集（即）且为无循环图，使得的 w(T) 最小，则 T 为 G 的最小生成树。",
    continue: !0
  }, {
    string: "简单来说，最小生成树就是要在图中找到一个包含所有节点的子集，这个子集所有边的权值之和最小，并且这个子集不能形成循环。",
    continue: !0
  }, {
    string: "Kruskal算法通过以下三个步骤查找最小生成树。",
    continue: !0
  }, {
    string: "1.将原图中所有的边按权值从小到大排序。2.在不构成循环的前提下，每次都选择权值最小的边。3.重复上一步骤，直到图中所有的节点都被选中。",
    continue: !0
  }, {
    string: "下面我们以上图为例，来演示以下如何使用Kruskal算法查找最小生成树。",
    continue: !0
  }, {
    string: "首先，找到当前权值最小的边，即 7-6(权值为1)。此时没有形成循环，因此选中这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 2-8(权值为2)。此时没有形成循环，选中这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 6-5(权值为2)。此时没有形成循环，选中这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 0-1(权值为4)。此时没有形成循环，选中这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 2-5(权值为4)。此时没有形成循环，选中这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 8-6(权值为6)。如果选中这条边，就形成了循环，所以舍弃这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 8-6(权值为6)。如果选中这条边，就形成了循环，所以舍弃这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 2-3(权值为7)。此时没有形成循环，选中这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 7-8(权值为7)。如果选中这条边，就形成了循环，所以舍弃这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 7-8(权值为7)。如果选中这条边，就形成了循环，所以舍弃这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 0-7(权值为8)。此时没有形成循环，选中这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 1-2(权值为8)。如果选中这条边，就形成了循环，所以舍弃这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 1-2(权值为8)。如果选中这条边，就形成了循环，所以舍弃这条边。",
    continue: !1
  }, {
    string: "继续寻找当前权值最小的边，即 3-4(权值为9)。此时没有形成循环，选中这条边。",
    continue: !1
  }, {
    string: "此时图中所有的点都被选中，这就找到了一颗最小生成树。",
    continue: !0
  }, {
    string: "到这里，我们完成了对Kruskal算法的演示。",
    continue: !1
  }],
  size: 23
}],
    K = [{
  Text: [{
    string: "Dijkstra（迪杰斯特拉）算法是典型最短路径算法，用于计算一个节点到其他节点的最短路径。它是贪婪算法的一种典型应用。",
    continue: !0
  }, {
    string: "Dijkstra 的主要特点是以起始点为中心向外层层拓展（广度优先搜索思想），直到拓展到终点为止。",
    continue: !0
  }, {
    string: "Dijkstra 算法步骤是： 1.建立一个集合S，集合包含已求出的最短路径的点。2.初始化集合。初始时，只有当前要计算的节点。",
    continue: !0
  }, {
    string: "3.找到当前选中节点的所有相邻节点, 然后选中其中较小的距离(权值)。4.基于选中的节点更新相邻节点的加权距离(权值)。5.重复 3 和 4，直到选中所有的节点。",
    continue: !0
  }, {
    string: "下面我们以 A 为起始节点来演示一下如何使用 Dijkstra 算法求出到其它节点的最短路径。",
    continue: !0
  }, {
    string: "首先选中 A, 将 A 加入集合 S。此时集合中只有初始节点 A, 它到达自身的距离为 0。",
    continue: !1
  }, {
    string: "首先选中 A, 将 A 加入集合 S。此时集合中只有初始节点 A, 它到达自身的距离为 0。",
    continue: !1
  }, {
    string: "找到 A 的相邻节点 B 和 F, A 到 B 的 距离为 4， 到 F 的距离为 8。因此选择 B 作为下一个节点。",
    continue: !1
  }, {
    string: "找到 A 的相邻节点 B 和 F, A 到 B 的 距离为 4， 到 F 的距离为 8。因此选择 B 作为下一个节点。",
    continue: !1
  }, {
    string: "B 的相邻节点为 C 和 F，更新 A 到 C 和 F 的最短加权路径， 分别为 12 和 8。",
    continue: !1
  }, {
    string: "因为 A 到 F 的路径更短，此时我们选中 F 作为下一个节点。",
    continue: !1
  }, {
    string: "F 的相邻节点为 I 和 G，更新 A 到 I 和 G 的最短加权路径， 分别为 15 和 9。",
    continue: !1
  }, {
    string: "因为 A 到 G 的路径更短，此时我们选中 G 作为下一个节点。",
    continue: !1
  }, {
    string: "G 的相邻节点为 I 和 H，更新 A 到 I 和 H 的最短加权路径， 分别为 15 和 11。",
    continue: !1
  }, {
    string: "因为 A 到 H 的路径更短，此时我们选中 H 作为下一个节点。",
    continue: !1
  }, {
    string: "H 的相邻节点为 C, D 和 E，更新 A 到 C, D 和 E 的最短加权路径， 分别为 12, 25 和 21。",
    continue: !1
  }, {
    string: "因为 A 到 C 的路径更短，此时我们选中 C 作为下一个节点。",
    continue: !1
  }, {
    string: "C 的相邻节点为 I 和 D，更新 A 到 I 和 D 的最短加权路径， 分别为 14 和 19。",
    continue: !1
  }, {
    string: "因为 A 到 I 的路径更短，此时我们选中 I 作为下一个节点。",
    continue: !1
  }, {
    string: "I 的相邻节点已经全部被选中，无需更新。此时还剩下 D 和 E 未被选中。",
    continue: !0
  }, {
    string: "因为 A 到 D 的路径更短，此时我们选中 D 作为下一个节点。",
    continue: !1
  }, {
    string: "最后选中节点 E。",
    continue: !1
  }, {
    string: "此时图中所有的点都被选中，此时我们就找到了节点 A 到其它所有节点的最短路径。",
    continue: !0
  }, {
    string: "到这里，我们完成了对Dijkstra算法的演示。",
    continue: !1
  }],
  size: 24
}],
    y = [{
  Text: [{
    string: "动态规划是通过把问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题，动态规划的算法复杂度往往优于朴素解法。",
    continue: !0
  }, {
    string: "动态规划的思路是: 1.将复杂的问题分解为简单的子问题。 2.对子问题逐一求解。 3。合并子问题的解，得到原问题的解。",
    continue: !0
  }, {
    string: "下面我们来演示一下如何运用动态规划求最长上升子序列问题。",
    continue: !0
  }, {
    string: "例如在上图的序列中，1，2，3，4 是它的最长上升子序列。",
    continue: !1
  }, {
    string: "那么如何求解呢？当然我们可以遍历序列中每一个数，用穷举法列出所有的上升序列，再找到最长的序列。但是这样的算法复杂度太高。",
    continue: !0
  }, {
    string: "我们现在用动态规划试一下，看看有没有更好的方法。",
    continue: !0
  }, {
    string: "根据动态规划的定义，首先我们需要把原来的问题分解成相似的子问题。假设原来的问题是求LIS(n)，现在我们需要找的就是 LIS(n) 和 LIS(k) 之间的关系(1<=k<=n)。",
    continue: !1
  }, {
    string: "对于上图中的序列，我们从K=1开始, 逐一列出最长上升子序列LIS(K)。",
    continue: !1
  }, {
    string: "对于上图中的序列，我们从K=1开始, 逐一列出最长上升子序列LIS(K)。",
    continue: !1
  }, {
    string: "对于上图中的序列，我们从K=1开始, 逐一列出最长上升子序列LIS(K)。",
    continue: !1
  }, {
    string: "对于上图中的序列，我们从K=1开始, 逐一列出最长上升子序列LIS(K)。",
    continue: !1
  }, {
    string: "对于上图中的序列，我们从K=1开始, 逐一列出最长上升子序列LIS(K)。",
    continue: !1
  }, {
    string: "对于上图中的序列，我们从K=1开始, 逐一列出最长上升子序列LIS(K)。",
    continue: !1
  }, {
    string: "对于上图中的序列，我们从K=1开始, 逐一列出最长上升子序列LIS(K)。",
    continue: !1
  }, {
    string: "这里我们可以看到，LIS(n+1)要么等于LIS(n)，要么加了1。",
    continue: !0
  }, {
    string: "其实也很好理解，原理就是，在前面所有的LIS种找到一个最长的LIS(i), 如果LIS(n)的尾项A(n)比LIS(i)的尾项A(i)要大，则LIS(n)=LIS(i)+1，否则LIS(n)=LIS(i)。",
    continue: !1
  }, {
    string: "也就是说，计算LIS(n)需要计算之前所有的LIS(K), 其中1<=k<=n。",
    continue: !0
  }, {
    string: "因此如果我们要求解LIS(6), 就需要求出LIS(1)~LIS(5)",
    continue: !1
  }, {
    string: "因此如果我们要求解LIS(6), 就需要求出LIS(1)~LIS(5)",
    continue: !1
  }, {
    string: "要求解LIS(5), 就需要求出LIS(1)~LIS(4)。",
    continue: !1
  }, {
    string: "要求解LIS(4), 就需要求出LIS(1)~LIS(3)。",
    continue: !1
  }, {
    string: "要求解LIS(3), 就需要求出LIS(1)~LIS(2)。",
    continue: !1
  }, {
    string: "要求解LIS(2), 就需要求出LIS(1)。",
    continue: !1
  }, {
    string: "我们可以储存子问题的结果，让每个子问题只被计算一次。需要计算的子问题就只剩下蓝色标出的部分了。",
    continue: !1
  }, {
    string: "采用了动态规划之后，时间复杂度大大降低了，动态规划求解的算法复杂度是O(n^2)。具体实现可以参考源代码。",
    continue: !0
  }, {
    string: "到这里，我们完成了对动态规划-最长上升子序列算法的演示。",
    continue: !1
  }],
  size: 26
}],
    j = [{
  Text: [{
    string: "汉诺塔问题是一个源于印度的古老数学问题。该问题的主要材料包括三根高度相同的柱子和一些大小及颜色不同的圆盘，三根柱子分别为起始A、辅助柱B及目标柱C。",
    continue: !0
  }, {
    string: "游戏的目标是把杆A上的圆盘全部移到杆C上，并仍保持原有顺序叠好。操作规则：每次只能移动一个盘子，并且在移动过程中三根杆上都始终保持大盘在下，小盘在上，操作过程中盘子可以置于A、B、C任一杆上。",
    continue: !0
  }, {
    string: "解法的基本思想是递归。目标是把A柱上的圆盘全部移到C柱。那么先把A柱顶部的N-1块圆盘移动到B住，再把A柱剩下的大盘移到C，最后把B塔的N-1块圆盘移到C。",
    continue: !0
  }, {
    string: "下面我们以三块圆盘为例来演示汉诺塔的递归解法。",
    continue: !0
  }, {
    string: "首先将圆盘1从A柱移动到B柱。",
    continue: !1
  }, {
    string: "将圆盘2从A柱移动到C柱。",
    continue: !1
  }, {
    string: "将圆盘1从B柱移动到C柱。",
    continue: !1
  }, {
    string: "将圆盘3从A柱移动到B柱。",
    continue: !1
  }, {
    string: "将圆盘1从C柱移动到A柱。",
    continue: !1
  }, {
    string: "将圆盘2从C柱移动到B柱。",
    continue: !1
  }, {
    string: "将圆盘1从A柱移动到B柱。",
    continue: !1
  }, {
    string: "将圆盘4从A柱移动到C柱。",
    continue: !1
  }, {
    string: "此时我们完成了递归的第一轮操作，即将N-1(N=4)块盘移动到B，把大盘移动到C柱。",
    continue: !0
  }, {
    string: "下面继续对剩下的圆盘使用同样的递归方法进行移动。",
    continue: !0
  }, {
    string: "将圆盘1从B柱移动到C柱。",
    continue: !1
  }, {
    string: "将圆盘2从B柱移动到A柱。",
    continue: !1
  }, {
    string: "将圆盘1从C柱移动到A柱。",
    continue: !1
  }, {
    string: "将圆盘3从B柱移动到C柱。",
    continue: !1
  }, {
    string: "将圆盘1从A柱移动到B柱。",
    continue: !1
  }, {
    string: "将圆盘2从A柱移动到C柱。",
    continue: !1
  }, {
    string: "将圆盘1从B柱移动到C柱。",
    continue: !1
  }, {
    string: "现在所有的圆盘都按要求从A柱移动到了C柱。",
    continue: !0
  }, {
    string: "到这里，我们完成了对汉诺塔问题的的演示。",
    continue: !1
  }],
  size: 23
}],
    G = [{
  Text: [{
    string: "题目：给定一个整数数组 nums 和一个目标值 target，请你在该数组中找出和为目标值的那两个整数，并返回他们的数组下标。",
    continue: !0
  }, {
    string: "例如这里我们的目标值 target为9。数组为[2, 11, 6, 7, 14]， 需要在数组中找到和为9两个数。",
    continue: !0
  }, {
    string: "这里我们借助哈希表来解决问题。",
    continue: !0
  }, {
    string: "创建一个哈希表。对于数组中的每一个元素x, 首先在哈希表中查询是否存在target-x, 如果不存在，则将它插入到哈希表中，其中key为当前元素值，value为其数组下标。如果存在则查找结束，返回两个数对应的数组下标。",
    continue: !0
  }, {
    string: "创建一个哈希表。对于数组中的每一个元素x, 首先在哈希表中查询是否存在target-x, 如果不存在，则将它插入到哈希表中，其中key为当前元素值，value为其数组下标。如果存在则查找结束，返回两个数对应的数组下标。",
    continue: !1
  }, {
    string: "对于数组中第一个元素 2, 直接插入哈希表。",
    continue: !0
  }, {
    string: "对于数组中第一个元素 2, 直接插入哈希表。",
    continue: !1
  }, {
    string: "数组中第二个元素是11, 查询哈希表，发现哈希表中没有元素与11相加可以得到9， 因此将11插入哈希表。",
    continue: !0
  }, {
    string: "数组中第二个元素是11, 查询哈希表，发现哈希表中没有元素与11相加可以得到9， 因此将11插入哈希表。",
    continue: !1
  }, {
    string: "继续遍历数组，第三个元素是6, 查询哈希表，发现哈希表中没有元素与6相加可以得到9， 因此将6插入哈希表。",
    continue: !0
  }, {
    string: "继续遍历数组，第三个元素是6, 查询哈希表，发现哈希表中没有元素与6相加可以得到9， 因此将6插入哈希表。",
    continue: !1
  }, {
    string: "继续遍历数组，下一个个元素是7, 查询哈希表，发现哈希表中元素2和7相加可以得到9。",
    continue: !0
  }, {
    string: "此时完成了查找，返回2和7的数组下标[0, 3]。",
    continue: !1
  }, {
    string: "到这里，我们完成了对该问题的的演示。",
    continue: !1
  }],
  size: 14
}],
    d = [{
  Text: [{
    string: "题目：给出两个非空的链表用来表示两个非负的整数。其中，它们各自的位数是按照逆序的方式存储的，并且它们的每个节点只能存储一位数字。我们将这两个数相加起来，则会返回一个新的链表来表示它们的和。",
    continue: !0
  }, {
    string: "因为链表中的位数是逆序存储的，因此可以遍历链表，将每个位的数字相加。注意要考虑是否有进位。",
    continue: !0
  }, {
    string: "一上图的两个链表为例，首先将第一位相加。",
    continue: !1
  }, {
    string: "我们用Carry来表示进位，第一位不需要考虑进位，因此Carry为0。",
    continue: !1
  }, {
    string: "相加的结果为7。",
    continue: !1
  }, {
    string: "再将链表的第二位相加。",
    continue: !1
  }, {
    string: "相加的结果为0, 并向下一位进1。",
    continue: !1
  }, {
    string: "再将链表的第三位相加，要考虑进位 Carry=1。",
    continue: !1
  }, {
    string: "相加的结果为8。",
    continue: !1
  }, {
    string: "此时我们已经得到表示相加结果的链表。",
    continue: !1
  }, {
    string: "注意这里有两个特殊情况需要考虑：1.如果两个链表的长度不同，则可以认为长度短的链表的后面有若干个0。2.如果链表遍历结束后Carry>0, 还需要在链表的结尾添加一个节点，值为Carry。",
    continue: !0
  }, {
    string: "到这里，我们完成了对该问题的的演示。",
    continue: !1
  }],
  size: 12
}],
    w = [{
  Text: [{
    string: '题目：给定一个字符串，请你找出其中不含有重复字符的最长子串的长度。例如对于字符串"PWWKEW"来说，最长子串是"WKE", 因此输出为3。',
    continue: !0
  }, {
    string: "我们就可以使用「滑动窗口」来解决这个问题。",
    continue: !0
  }, {
    string: "我们使用一个滑动窗口，包含符串中的某个子串的左右边界。在每一步的操作中，我们将窗口的左边界向右移动，再不断地右移右边界。在此过程中保证两个指针对应的子串中没有重复的字符。并记录最大子串长度。",
    continue: !0
  }, {
    string: "例如对于上图中的字符串，我们首先建立一个滑动窗口，包含字符串的第一个字符，此时最大子串长度为1。",
    continue: !1
  }, {
    string: "判断下一个字符是否已经在当前子串中出现过。",
    continue: !1
  }, {
    string: "如果没有出现，则将窗口的右边界向右移动一格，并更新当前的最长子串长度为2。",
    continue: !1
  }, {
    string: "再判断下一个字符是否在当前子串中出现。",
    continue: !1
  }, {
    string: "此时发现 当前子串已经包含字符 W。此时右移窗口的左边界，使其刚好排除第一个W 。",
    continue: !0
  }, {
    string: "此时发现 当前子串已经包含字符 W。此时右移窗口的左边界，使其刚好排除第一个W 。",
    continue: !1
  }, {
    string: "再重复上述过程，不断移动窗口，直到窗口的右边界达到字符串尾部。",
    continue: !1
  }, {
    string: "再重复上述过程，不断移动窗口，直到窗口的右边界达到字符串尾部。",
    continue: !1
  }, {
    string: "再重复上述过程，不断移动窗口，直到窗口的右边界达到字符串尾部。",
    continue: !1
  }, {
    string: "再重复上述过程，不断移动窗口，直到窗口的右边界达到字符串尾部。",
    continue: !1
  }, {
    string: "再重复上述过程，不断移动窗口，直到窗口的右边界达到字符串尾部。",
    continue: !1
  }, {
    string: "当滑动窗口的右边界到达字符串尾部，结束遍历。此时得到最大子串长度为 3。",
    continue: !1
  }, {
    string: "到这里，我们完成了对该问题的的演示。",
    continue: !1
  }],
  size: 16
}],
    P = [{
  Text: [{
    string: '题目：给定一个字符串 s，找到 s 中最长的回文子串。例如输入: "babad"，输出: "bab" 或 "aba"。',
    continue: !0
  }, {
    string: "这里我们使用「中心扩散法」来解决这个问题。",
    continue: !0
  }, {
    string: "回文字符串有一个特点，就是中心对称。即从字符串的中间分别向两端遍历，所经过的字符是相同的。",
    continue: !0
  }, {
    string: "下面我们从头开始遍历字符串。",
    continue: !1
  }, {
    string: "下面我们从头开始遍历字符串。",
    continue: !1
  }, {
    string: "从第二个字符开始，向两边扩散。",
    continue: !1
  }, {
    string: "从第二个字符开始，向两边扩散。",
    continue: !1
  }, {
    string: "发现两边的字符不相等，因此继续向前遍历。",
    continue: !1
  }, {
    string: "选中第三个字符开始向两边扩散。",
    continue: !1
  }, {
    string: "选中第三个字符开始向两边扩散。",
    continue: !1
  }, {
    string: "发现两边的字符不相等，因此继续向前遍历。",
    continue: !1
  }, {
    string: "选中第四个字符开始向两边扩散。",
    continue: !1
  }, {
    string: "发现两边的字符相等，可以继续扩散。",
    continue: !1
  }, {
    string: "发现两边的字符相等，可以继续扩散。",
    continue: !1
  }, {
    string: '此时两边的字符不相等，此时最长的回文是"ACYCA"。继续向前遍历。',
    continue: !1
  }, {
    string: "选中第五个字符开始向两边扩散。",
    continue: !1
  }, {
    string: "发现两边的字符不相等，因此继续向前遍历。",
    continue: !1
  }, {
    string: "选中第六个字符开始向两边扩散。",
    continue: !1
  }, {
    string: "发现两边的字符不相等，因此继续向前遍历。",
    continue: !1
  }, {
    string: "选中第七个字符开始向两边扩散。",
    continue: !1
  }, {
    string: "此时发现虽然左右两侧的字符不相等，但是右侧的字符也是E, 此时以这两个E为中心也可以构成回文。",
    continue: !1
  }, {
    string: "我们以这两个字符为中心进行扩散。",
    continue: !0
  }, {
    string: '两边的字符相等，构成回文"AEEA"。',
    continue: !1
  }, {
    string: '此时到达了字符串的尾部，遍历结束，找到的最长回文子串是"ACYCA"。',
    continue: !1
  }, {
    string: "到这里，我们完成了对该问题的的演示。",
    continue: !1
  }],
  size: 25
}],
    m = [{
  Text: [{
    string: "题目：给你 n 个非负整数 a1，a2，...，an，每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线，垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线，使得它们与 x 轴共同构成的容器可以容纳最多的水。",
    continue: !0
  }, {
    string: "这里我们使用「双指针」来解决这个问题。(关于双指针法的正确性，可以参考LeetCode官方的证明)",
    continue: !0
  }, {
    string: "开始时，我们将两个指针分别指向容器的左右两端，并计算出此时容纳水的量。",
    continue: !0
  }, {
    string: "开始时，我们将两个指针分别指向容器的左右两端，并计算出此时容纳水的量。",
    continue: !1
  }, {
    string: "然后我们需要移动一个指针，这里的策略是每次都移动较短的那一个边。",
    continue: !0
  }, {
    string: "因此，将左指针向右移动一格，并计算当前容纳水的量。",
    continue: !1
  }, {
    string: "此时右侧的边更短，因此将右指针向左移动一格，并重新计算容纳水的量。",
    continue: !0
  }, {
    string: "此时右侧的边更短，因此将右指针向左移动一格，并重新计算容纳水的量。",
    continue: !1
  }, {
    string: "以此类推，不断移动左右指针，直到两个指针重合。",
    continue: !1
  }, {
    string: "以此类推，不断移动左右指针，直到两个指针重合，",
    continue: !1
  }, {
    string: "以此类推，不断移动左右指针，直到两个指针重合。",
    continue: !1
  }, {
    string: "以此类推，不断移动左右指针，直到两个指针重合。",
    continue: !1
  }, {
    string: "以此类推，不断移动左右指针，直到两个指针重合。",
    continue: !1
  }, {
    string: "此时两个指针重合，最大容纳水的量是 49。",
    continue: !0
  }, {
    string: "到这里，我们完成了对该问题的的演示。",
    continue: !1
  }],
  size: 15
}],
    p = [{
  Text: [{
    string: "题目：给定一个链表，删除链表的倒数第 n 个节点，并且返回链表的头结点。",
    continue: !0
  }, {
    string: "这里我们使用「快慢指针」来解决这个问题。用快、慢两个指针同时对链表进行遍历，并且快指针比慢指针超前n个节点。",
    continue: !0
  }, {
    string: "比如这里假设我们要删除倒数第2个节点。开始时慢指针指向链表头，快指针指向index=2的位置",
    continue: !1
  }, {
    string: "同时移动两个指针。当快指针遍历到链表的末尾时，慢指针就恰好处于倒数第 2 个节点。",
    continue: !1
  }, {
    string: "同时移动两个指针。当快指针遍历到链表的末尾时，慢指针就恰好处于倒数第 2 个节点。",
    continue: !1
  }, {
    string: "同时移动两个指针。当快指针遍历到链表的末尾时，慢指针就恰好处于倒数第 2 个节点。",
    continue: !1
  }, {
    string: "此时快指针到达链表末尾，慢指针指向了我们要删除的倒数第2个节点。",
    continue: !0
  }, {
    string: "此时删除慢指针指向的节点，并将其前置节点的next指向下一个节点。",
    continue: !1
  }, {
    string: "此时删除慢指针指向的节点，并将其前置节点的next指向下一个节点。",
    continue: !1
  }, {
    string: "到这里，我们完成了对该问题的的演示。",
    continue: !1
  }],
  size: 10
}],
    f = [{
  Text: [{
    string: "题目：给定一个链表，删除链表的倒数第 n 个节点，并且返回链表的头结点。",
    continue: !0
  }, {
    string: "这里我们使用「快慢指针」来解决这个问题。用快、慢两个指针同时对链表进行遍历，并且快指针比慢指针超前n个节点。",
    continue: !0
  }, {
    string: "比如这里假设我们要删除倒数第2个节点。开始时慢指针指向链表头，快指针指向index=2的位置",
    continue: !1
  }, {
    string: "同时移动两个指针。当快指针遍历到链表的末尾时，慢指针就恰好处于倒数第 2 个节点。",
    continue: !1
  }, {
    string: "同时移动两个指针。当快指针遍历到链表的末尾时，慢指针就恰好处于倒数第 2 个节点。",
    continue: !1
  }, {
    string: "同时移动两个指针。当快指针遍历到链表的末尾时，慢指针就恰好处于倒数第 2 个节点。",
    continue: !1
  }, {
    string: "此时快指针到达链表末尾，慢指针指向了我们要删除的倒数第2个节点。",
    continue: !0
  }, {
    string: "此时删除慢指针指向的节点，并将其前置节点的next指向下一个节点。",
    continue: !1
  }, {
    string: "此时删除慢指针指向的节点，并将其前置节点的next指向下一个节点。",
    continue: !1
  }, {
    string: "到这里，我们完成了对该问题的的演示。",
    continue: !1
  }],
  size: 10
}],
    N = [{
  Text: [{
    string: "题目：一个机器人位于一个 m x n 网格的左上角(Start)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(Finish)。问总共有多少条不同的路径。",
    continue: !0
  }, {
    string: "这里我们使用动态规划解决问题。因为每次只能向下或者向右移动，因此如果要走到终点(Finish), 那么上一步经过的格子必然是必然是终点的左边或者上边的格子。",
    continue: !1
  }, {
    string: "这里我们使用动态规划解决问题。因为每次只能向下或者向右移动，因此如果要走到终点(Finish), 那么上一步经过的格子必然是必然是终点的左边或者上边的格子。",
    continue: !1
  }, {
    string: "总结规律可以发现，对于网格中的任何一个格子(i, j), 上一步经过的格子是(i-1, j) 或者(i, j-1)。",
    continue: !1
  }, {
    string: "总结规律可以发现，对于网格中的任何一个格子(i, j), 上一步经过的格子是(i-1, j) 或者(i, j-1)。",
    continue: !1
  }, {
    string: "总结规律可以发现，对于网格中的任何一个格子(i, j), 上一步经过的格子是(i-1, j) 或者(i, j-1)。",
    continue: !1
  }, {
    string: "我们用 f(i, j)f(i,j) 表示从左上角走到 (i, j)(i,j) 的路径数量。可以得到动态规划转移方程：f(i, j) = f(i−1, j)+f(i, j−1)",
    continue: !1
  }, {
    string: "我们从起始点开始，依次计算每个格子的路径数量。",
    continue: !0
  }, {
    string: "这里第一排的格子，由于从起始点开始只能向右移动才能到达，因此路径数都为1。",
    continue: !1
  }, {
    string: "同样的，第一列格子，由于从起始点开始只能向下移动才能到达，因此路径数也都为1。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "对于剩下的格子，我们根据转移方程可以逐个求出路径数。",
    continue: !1
  }, {
    string: "到这里，我们完成了对该问题的的演示。",
    continue: !1
  }],
  size: 24
}],
    V = [{
  Text: [{
    string: "题目：给定一个二叉树的根节点 root ，返回它的中序遍历。",
    continue: !0
  }, {
    string: "首先我们需要了解什么是二叉树的中序遍历：按照访问左子树——根节点——右子树的方式遍历这棵树，而在访问左子树或者右子树的时候我们按照同样的方式遍历，直到遍历完整棵树。",
    continue: !0
  }, {
    string: "这里我们使用一个栈来辅助解决问题。按照中序遍历的规则，从树的根节点开始，每到一个节点P ，先将其入栈，再遍历其左子树，接着访问节点P，最后遍历右子树。出栈的顺序代表遍历的顺序。",
    continue: !0
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !0
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "点击 [前进] 查看完整遍历过程。",
    continue: !1
  }, {
    string: "到这里，我们完成了对该问题的的演示。",
    continue: !1
  }],
  size: 24
}];
module.exports = {
  BubbleSort: n,
  SelectionSort: i,
  InsertionSort: t,
  FastSort: s,
  HeapSort: e,
  MergeSort: u,
  ShellSort: r,
  LinearSearch: o,
  BinarySearch: g,
  InsertionSearch: c,
  _Array: a,
  SingleLinkedList: A,
  Stack: l,
  Queue: B,
  BinaryTree: C,
  FullBinaryTree: D,
  Heap: I,
  Graph: L,
  ExpressionOfGraph: x,
  GraphDFS: E,
  GraphBFS: T,
  RunLengthEncoding: z,
  HuffmanEncoding: h,
  Hash: F,
  BinarySearchTree: S,
  Kruskal: k,
  Dijkstra: K,
  LIS: y,
  HanoiTower: j,
  SumOfTwoNumbers: G,
  AddTwoNumbers: d,
  LongestSubString: w,
  LongestPalindromicString: P,
  ContainerWithMostWater: m,
  RemoveNthNodeFromEndofList: p,
  ImplementStackUsingQueues: f,
  UniquePaths: N,
  BinaryTreeInorderTraversal: V
};